﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Fabrication.Tests.Mocks;
using Fabrication.Tests.TestClasses;
using NUnit.Framework;

namespace Fabrication.Tests
{
    [TestFixture]
    public class fabricator
    {
        [Test]
        public void should_have_static_equivalent_of_all_public_scope_methods()
        {
            var fabricatorType = typeof (Fabricator);
            var scopeType = typeof (FabricationScope);

            var scopeMethods = scopeType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            var fabricatorMethods = fabricatorType.GetMethods(BindingFlags.Public | BindingFlags.Static);

            var missingMethods = scopeMethods.Except(fabricatorMethods, new MethodInfoComparer());

            Assert.That(missingMethods, Is.Empty, string.Format("No static accessor for method: {0}", missingMethods.FirstOrDefault()));
        }

        [Test]
        public void should_overwrite_previous_setup_when_calling_setup()
        {
            Fabricator.Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).Use("OriginalValue");

            Fabricator.Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).Use("OverwrittenValue");

            var builtItem = Fabricator.Generate<SinglePropertyClass<string>>();

            Assert.That(builtItem.Value, Is.EqualTo("OverwrittenValue"));
        }

        // TODO: Split tests (e.g. ignore non existent removals)
        [Test]
        public void should_not_throw_exceptions_in_global_setup_methods()
        {
            Assert.DoesNotThrow(() =>
                                    {
                                        Fabricator.RemoveConvention("UnknownConvention");
                                        Fabricator.RemoveConvention<string>("NoConvention");
                                        Fabricator.RemoveAllNamedConventions();
                                        Fabricator.SetConvention<int, MockConstantValueProvider<int>>();
                                        Fabricator.SetConvention<int, MockConstantValueProvider<int>>("Property");
                                        Fabricator.SetConvention<int>(new MockConstantValueProvider<int>(), "Property");
                                    });
        }

        #region MethodInfoComparer

        private class MethodInfoComparer : EqualityComparer<MethodInfo>
        {
            public override bool Equals(MethodInfo x, MethodInfo y)
            {
                if (x.Name != y.Name)
                    return false;

                var xParams = x.GetParameters();
                var yParams = y.GetParameters();

                if (xParams.Count() != yParams.Count())
                    return false;

                for (int i = 0; i < xParams.Count(); i++)
                {
                    if (xParams[i].ParameterType != yParams[i].ParameterType)
                        return false;
                }

                return x.GetGenericArguments().Count() == y.GetGenericArguments().Count();
            }

            // TODO: This is going to be slow. It's only used in a test method, but still....
            public override int GetHashCode(MethodInfo obj)
            {
                if (obj == null)
                    return 0;

                unchecked
                {
                    return obj.Name.GetHashCode() ^ obj.GetGenericArguments().Count() ^ obj.GetParameters().Count();
                }
            }
        }

        #endregion
    }
}
